package io.github.topcatv.mall.common.security.jwt;

import com.google.gson.Gson;
import io.github.topcatv.mall.common.ao.ReturnObject;
import io.github.topcatv.mall.common.security.CustomUserDetails;
import io.github.topcatv.mall.common.util.ResponseUtil;
import io.github.topcatv.mall.model.sys.entity.LoginLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Map;

/**
 * @author liuyi
 */
@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RestTemplate restTemplate;

    private Gson gson = new Gson();

    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Value("${jwt.header}")
    private String header;

    public JwtLoginFilter(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = "";
        String password = "";

        try {
            String jsonBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            Map<String, String> map = gson.fromJson(jsonBody, Map.class);
            username = map.get("username");
            password = map.get("password");
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.warn("JwtLoginFilter attemptAuthentication user {}, pwd {}", username, password);
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
        return this.getAuthenticationManager().authenticate(token);
    }

    protected String getIpAddress(HttpServletRequest request) throws IOException {
        // 获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址
        String ip = request.getHeader("X-Forwarded-For");
        if (log.isInfoEnabled()) {
            log.info("getIpAddress(HttpServletRequest) - X-Forwarded-For - String ip=" + ip);
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
                if (log.isInfoEnabled()) {
                    log.info("getIpAddress(HttpServletRequest) - Proxy-Client-IP - String ip=" + ip);
                }
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
                if (log.isInfoEnabled()) {
                    log.info("getIpAddress(HttpServletRequest) - WL-Proxy-Client-IP - String ip=" + ip);
                }
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
                if (log.isInfoEnabled()) {
                    log.info("getIpAddress(HttpServletRequest) - HTTP_CLIENT_IP - String ip=" + ip);
                }
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
                if (log.isInfoEnabled()) {
                    log.info("getIpAddress(HttpServletRequest) - HTTP_X_FORWARDED_FOR - String ip=" + ip);
                }
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                if (log.isInfoEnabled()) {
                    log.info("getIpAddress(HttpServletRequest) - getRemoteAddr - String ip=" + ip);
                }
            }
        } else if (ip.length() > 15) {
            String[] ips = ip.split(",");
            for (String strIp : ips) {
                if (!("unknown".equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        log.warn("JwtLoginFilter successfulAuthentication");
        // Credentials was protected, it is null
        CustomUserDetails user = (CustomUserDetails) authResult.getPrincipal();
        recordLoginLog(request, user);
        String token = jwtTokenUtil.generateToken(user);
        response.addHeader(header, String.format("%s %s", tokenHead, token));
        ResponseUtil.responseText(response, ReturnObject.success().put("token", token));
    }

    private void recordLoginLog(HttpServletRequest request, CustomUserDetails user) throws IOException {
        LoginLog loginLog = new LoginLog();
        loginLog.setUsername(user.getUsername());
        loginLog.setLoginTime(LocalDateTime.now());
        loginLog.setIp(getIpAddress(request));
//        loginLog.setLocate(getLocate(loginLog.getIp()));
        mongoTemplate.insert(loginLog);
    }

    private String getLocate(String ip) {
        //新浪查询失败查询阿里
        String sina = restTemplate.getForObject("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip={ip}", String.class, ip);
        SinaIpVo sinaIpVo = new Gson().fromJson(sina, SinaIpVo.class);
        if (sinaIpVo.getRet() != -1) {
            return sinaIpVo.getCity();
        } else {
            String object = restTemplate.getForObject("http://ip.taobao.com/service/getIpInfo.php?ip={ip}", String.class, ip);
            IpVo ipVo = new Gson().fromJson(object, IpVo.class);
            // XX表示内网
            if (ipVo.getCode() == 0 && !ipVo.getAddress().getRegion().equals("XX")) {
                return ipVo.getAddress().getCity();
            }
        }
        return "未知";
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
        log.warn("JwtLoginFilter unsuccessfulAuthentication");
        String msg = failed.getMessage();
        if (failed instanceof BadCredentialsException || failed instanceof UsernameNotFoundException) {
            msg = "用户名或密码不正确";
        }
        if (failed instanceof LockedException) {
            msg = "账号已锁定";
        }
        if (failed instanceof CredentialsExpiredException) {
            msg = "密码已过期，请重置密码后登录";
        }
        ResponseUtil.responseText(response, ReturnObject.error(msg));
    }

    class SinaIpVo implements Serializable {
        private Integer ret;
        private String province;
        private String city;

        public Integer getRet() {
            return ret;
        }

        public void setRet(Integer ret) {
            this.ret = ret;
        }

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }
    }

    class IpVo implements Serializable {
        private Integer code;
        private Address address;

        public Integer getCode() {
            return code;
        }

        public void setCode(Integer code) {
            this.code = code;
        }

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        class Address implements Serializable {
            private String ip;
            private String region;
            private String city;

            public String getIp() {
                return ip;
            }

            public void setIp(String ip) {
                this.ip = ip;
            }

            public String getRegion() {
                return region;
            }

            public void setRegion(String region) {
                this.region = region;
            }

            public String getCity() {
                return city;
            }

            public void setCity(String city) {
                this.city = city;
            }
        }
    }

}